#include "../StdAfx.h"
#include "../ComPropVariant.h"
#include "../SevenZip/SevenZipArchive.h"
#include "../SevenZip/SevenZipArchiveItem.h"

#include "BindCtxUtils.h"
#include "IDListUtils.h"

#include "PropertyStoreFactory.h"
#include "EnumIDList.h"
#include "Folder.h"

using namespace SevenZip;

// {DDF585FF-1D53-4167-AC62-1C811CE48708}
EXTERN_C CONST GUID CLSID_SevenZipFolder =
         { 0xddf585ff, 0x1d53, 0x4167, { 0xac, 0x62, 0x1c, 0x81, 0x1c, 0xe4, 0x87, 0x8 } };

CONST ZIPFOLDER_COLUMN CSevenZipFolder::Columns[] =
{
	{ PKEY_ItemNameDisplay, SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_TYPE_STR  },
	{ PKEY_DateModified,    SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_TYPE_DATE },
	{ PKEY_Size,            SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_TYPE_INT  },
	{ PKEY_ItemTypeText,    SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_TYPE_STR  },
	{ PKEY_DateAccessed,    SHCOLSTATE_SECONDARYUI | SHCOLSTATE_TYPE_DATE },
	{ PKEY_DateCreated,     SHCOLSTATE_SECONDARYUI | SHCOLSTATE_TYPE_DATE },
};

HRESULT OutputDebugPropertyName(REFPROPERTYKEY Key)
{
	HRESULT hr = S_OK;

#ifdef DEBUG
	CComPtr<IPropertyDescription> Description;

	if (SUCCEEDED(hr = ::PSGetPropertyDescription(Key, IID_PPV_ARGS(&Description))))
	{
		CComMemPtr<WCHAR> Name;

		if (SUCCEEDED(hr = Description->GetCanonicalName(&Name)))
		{
			CStringW Buffer(Name);

			Buffer.AppendChar(L'\n');

			::OutputDebugStringW(Buffer);
		}
	}
#endif

	return hr;
}

// IPersist
HRESULT CSevenZipFolder::GetClassID(CLSID * ClassID)
{
	HRESULT hr = E_POINTER;

	if (ClassID != NULL)
	{
		*ClassID = GetObjectCLSID();
		hr = S_OK;
	}

	return hr;
}

HRESULT CSevenZipFolder::FinalConstruct()
{
	HRESULT hr = CoCreate(_Factory);

#ifdef _DEBUG
	if (SUCCEEDED(hr))
	{
		hr = _Factory->AddLibrary(L"D:\\u\\7-Zip\\x86\\4.64-2009-01-03\\7z.dll");
	}
#endif

	return hr;
}

VOID CSevenZipFolder::FinalRelease()
{
}

// IPersistFolder
IFACEMETHODIMP CSevenZipFolder::Initialize(PCIDLIST_ABSOLUTE ItemIDList)
{
	HRESULT hr;

	_Archive.Release();
	_Folder = NULL;

	if (SUCCEEDED(hr = CoCreate(_Archive)))
	{
		CComMemPtr<ITEMIDLIST_RELATIVE> Remainder;

		if (SUCCEEDED(hr = _GetPathFromItemIDList(ItemIDList, _ArchivePath, Remainder)))
		{
			if (SUCCEEDED(hr = _Archive->InitializeFromPath(_Factory, _ArchivePath)))
			{
				_Location.Free();
				
				if (SUCCEEDED(hr = ::CloneFullIDList(ItemIDList, &_Location)))
				{
					_Folder = _Archive->GetRoot();
					
					if (!::ILIsEmpty(Remainder))
					{
						_GetItemByItemIDList(Remainder, &_Folder);
					}
				}
			}
		}
	}
	
	return hr;
}

// IPersistFolder2
IFACEMETHODIMP CSevenZipFolder::GetCurFolder(PIDLIST_ABSOLUTE * ItemIDList)
{
	return ::CloneFullIDList(_Location, ItemIDList);
}


// IShellFolder
IFACEMETHODIMP CSevenZipFolder::ParseDisplayName(HWND, IBindCtx *, PWSTR, PULONG, PIDLIST_RELATIVE *, PULONG)
{
	return E_INVALIDARG;
}

IFACEMETHODIMP CSevenZipFolder::EnumObjects(HWND, SHCONTF Flags, IEnumIDList ** Pointer)
{
	HRESULT hr = E_POINTER;
	CComPtr<CSevenZipEnumIDList> Enum;

	if (Pointer != NULL)
	{
		if (SUCCEEDED(hr = CoCreate(Enum)))
		{
			CComPtrEx<CSevenZipFolder> self(this);

			if (SUCCEEDED(hr = Enum->Initialize(self, _Folder, Flags)))
			{
				*Pointer = Enum.Detach();
			}
		}
	}

	return hr;
}

IFACEMETHODIMP CSevenZipFolder::BindToObject(PCUIDLIST_RELATIVE ItemIDList, IBindCtx *, REFIID InterfaceID, VOID ** Pointer)
{
	if (Pointer == NULL)
	{
		return E_POINTER;
	}

	if (InterfaceID == IID_IShellFolder)
	{
		HRESULT hr;
		CComPtr<CSevenZipFolder> ChildFolder;

		if (SUCCEEDED(hr = CoCreate(ChildFolder)))
		{
			CComMemPtr<ITEMIDLIST_ABSOLUTE> FullIDList;

			if (SUCCEEDED(hr = CombineIDLists(_Location, ItemIDList, &FullIDList)))
			{
				if (SUCCEEDED(hr = ChildFolder->Initialize(FullIDList)))
				{
					hr = ChildFolder->QueryInterface(InterfaceID, Pointer);
				}
			}
		}

		return hr;
	}
	else if (InterfaceID == IID_IPropertyStoreFactory)
	{
		HRESULT hr;
		CComPtr<CPropertyStoreFactory> Factory;
		
		if (SUCCEEDED(hr = _CreatePropertyStoreFactory(ItemIDList, Factory)))
		{
			hr = Factory->QueryInterface(InterfaceID, Pointer);
		}

		return hr;
	}
	else if (InterfaceID == IID_IStream)
	{
		//HRESULT hr;
		//CSevenZipArchiveItem * Item;

		//if (SUCCEEDED(hr = _GetItemByItemIDList(ItemIDList, &Item)))
		//{
		//	
		//}

		//return hr;
	}

	return E_NOINTERFACE;
}

IFACEMETHODIMP CSevenZipFolder::BindToStorage(PCUIDLIST_RELATIVE ItemIDList, IBindCtx *, REFIID InterfaceID, VOID ** Pointer)
{
	return BindToObject(ItemIDList, NULL, InterfaceID, Pointer);
}

IFACEMETHODIMP CSevenZipFolder::CompareIDs(LPARAM, PCUIDLIST_RELATIVE x, PCUIDLIST_RELATIVE y)
{
	HRESULT hr;

	CComPropVariant xName;
	CComPropVariant yName;

	if (SUCCEEDED(hr = GetPropertyFromIDList(x, PKEY_ParsingName, &xName)))
	{
		if (SUCCEEDED(hr = GetPropertyFromIDList(y, PKEY_ParsingName, &yName)))
		{
			hr = CompareResultFromInt(::PropVariantCompare(xName, yName));
		}
	}

	return hr;
}

IFACEMETHODIMP CSevenZipFolder::CreateViewObject(HWND, REFIID InterfaceID, VOID ** Pointer)
{
	if (Pointer != NULL)
	{
		HRESULT hr = E_NOINTERFACE;

		if (InterfaceID == IID_IShellView)
		{
			SFV_CREATE SfvCreate = 
			{
				sizeof(SFV_CREATE) 
			};

			if (SUCCEEDED(hr = QueryInterface(IID_PPV_ARGS(&SfvCreate.pshf))))
			{
				hr = ::SHCreateShellFolderView(&SfvCreate, reinterpret_cast<IShellView **>(Pointer));
			}

			SfvCreate.pshf->Release();
		}
		else if (InterfaceID == IID_ITransferSource)
		{

		}

		return hr;
	}

	return E_POINTER;
}

IFACEMETHODIMP CSevenZipFolder::GetAttributesOf(UINT Count, PCUITEMID_CHILD_ARRAY ItemIDs, SFGAOF * Attributes)
{
	HRESULT hr = S_OK;

	if ((Attributes != NULL) && (ItemIDs != NULL))
	{
		SFGAOF Result = ULONG_MAX;

		for (UINT i = 0; SUCCEEDED(hr) && (i < Count); i++)
		{
			CSevenZipArchiveItem * Item;

			if (SUCCEEDED(hr = _GetItemByItemID(ItemIDs[i], &Item)))
			{
				BOOL IsFolder = FALSE;
				SFGAOF ThisResult = SFGAO_CANCOPY | 
									SFGAO_CANLINK;

				if (SUCCEEDED(hr = Item->IsFolder(&IsFolder)))
				{
					if (IsFolder)
					{
						ThisResult |= (SFGAO_BROWSABLE | SFGAO_FOLDER | SFGAO_HASSUBFOLDER);
					}
				}

				Result &= ThisResult;
			}
		}

		if (SUCCEEDED(hr))
		{
			if (Result == ULONG_MAX)
			{
				Result = 0;
			}

			hr = S_OK;
			*Attributes = (Result & *Attributes);
		}
	}

	return hr;
}

IFACEMETHODIMP CSevenZipFolder::GetUIObjectOf(HWND OwnerWindow, UINT ItemCount, PCUITEMID_CHILD_ARRAY ItemIDs, REFIID InterfaceID, UINT *, VOID ** Pointer)
{
	HRESULT hr = E_NOINTERFACE;

	if ((InterfaceID == IID_IContextMenu) ||
		(InterfaceID == IID_IContextMenu2) ||
		(InterfaceID == IID_IContextMenu3))
	{
		DEFCONTEXTMENU DefContextMenu =
		{
			OwnerWindow,
			NULL,
			_Location,
			this,
			ItemCount,
			ItemIDs,
			NULL,
			0,
			NULL
		};

		hr = ::SHCreateDefaultContextMenu(&DefContextMenu, InterfaceID, Pointer);
	}
	else if (InterfaceID == IID_IDataObject)
	{
		hr = ::SHCreateDataObject(_Location, ItemCount, ItemIDs, NULL, InterfaceID, Pointer);
	}
	else if (ItemCount == 1)
	{
		CSevenZipArchiveItem * Item = NULL;

		if (SUCCEEDED(hr = _GetItemByItemID(ItemIDs[0], &Item)))
		{
			hr = _GetUIObjectOfItem(Item, InterfaceID, Pointer);
		}
	}

	return hr;
}

IFACEMETHODIMP CSevenZipFolder::GetDisplayNameOf(PCUITEMID_CHILD ItemID, SHGDNF Flags, STRRET * Name)
{
	if ((::ILIsEmpty(ItemID)) || (Name == NULL))
	{
		return E_INVALIDARG;
	}

	HRESULT hr;
	CStringW Result;
	CSevenZipArchiveItem * Item;

	if (SUCCEEDED(hr = _GetItemByItemID(ItemID, &Item)))
	{
		if ((Flags & SHGDN_FORPARSING) && !(Flags & SHGDN_INFOLDER))
		{
			CStringW ItemPath;

			if (SUCCEEDED(hr = Item->GetPath(ItemPath)))
			{
				Result.Append(_ArchivePath);
				Result.Append(L"\\");
				Result.Append(ItemPath);
			}
		}
		else
		{
			hr = Item->GetName(Result);
		}
	}

	if (SUCCEEDED(hr))
	{
		hr = CComPropVariant(Result).ToStrRet(Name);
	}

	return hr;
}

IFACEMETHODIMP CSevenZipFolder::SetNameOf(HWND, PCUITEMID_CHILD, PCWSTR, DWORD, PITEMID_CHILD *)
{
	return E_NOTIMPL;
}

// IShellFolder2
IFACEMETHODIMP CSevenZipFolder::GetDefaultSearchGUID(LPGUID)
{
	return E_NOTIMPL;
}

IFACEMETHODIMP CSevenZipFolder::EnumSearches(__deref_out IEnumExtraSearch **)
{
	return E_NOTIMPL;
}

IFACEMETHODIMP CSevenZipFolder::GetDefaultColumn(DWORD, ULONG * Sort, ULONG * Display) 
{
	*Sort = 0;
	*Display = 0;

	return S_OK;
}

IFACEMETHODIMP CSevenZipFolder::GetDefaultColumnState(UINT Index, SHCOLSTATEF * ColumnState)
{
	if (Index < ARRAYSIZE(Columns))
	{
		*ColumnState = Columns[Index].ColumnState;
		return S_OK;
	}

	return E_INVALIDARG;
}

IFACEMETHODIMP CSevenZipFolder::GetDetailsEx(PCUITEMID_CHILD ItemID, CONST PROPERTYKEY * Property, LPVARIANT Result)
{
	HRESULT hr = E_FAIL;
	CComPropVariant PropVar;

	if (SUCCEEDED(hr = _GetDetailsEx(ItemID, *Property, PropVar)))
	{
		hr = PropVar.ToVariant(Result);
	}

	return hr;
}

IFACEMETHODIMP CSevenZipFolder::GetDetailsOf(PCUITEMID_CHILD ItemID, UINT Column, LPSHELLDETAILS Result)
{
	PROPERTYKEY Property;
	HRESULT hr;

	if (SUCCEEDED(hr = MapColumnToSCID(Column, &Property)))
	{
		CComPropVariant PropValue;
		CComPtr<IPropertyDescription> PropDesc;

		if (SUCCEEDED(hr = ::PSGetPropertyDescription(Property, IID_PPV_ARGS(&PropDesc))))
		{
			if (ItemID == NULL)
			{
				CComMemPtr<WCHAR> PropDisplayName;

				if (SUCCEEDED(hr = PropDesc->GetDisplayName(&PropDisplayName)))
				{
					PropValue = PropDisplayName;
				}
			}
			else
			{
				hr = _GetDetailsEx(ItemID, Property, PropValue);
			}

			if (SUCCEEDED(hr))
			{
				if (SUCCEEDED(hr = PropValue.ToStrRet(&Result->str)))
				{
					hr = PropDesc->GetDefaultColumnWidth(reinterpret_cast<PUINT>(&Result->cxChar));
				}
			}
		}
	}

	return hr;
}

IFACEMETHODIMP CSevenZipFolder::MapColumnToSCID(UINT Index, PROPERTYKEY * Property)
{
	if (Index < ARRAYSIZE(Columns))
	{
		*Property = Columns[Index].Property;
		return S_OK;
	}

	return E_INVALIDARG;
}

STDMETHODIMP CSevenZipFolder::_CreatePropertyStoreFactory(PCUIDLIST_RELATIVE ItemIDList, CComPtr<CPropertyStoreFactory> & Factory)
{
	HRESULT hr;
	CSevenZipArchiveItem * Item;

	if (SUCCEEDED(hr = _GetItemByItemIDList(ItemIDList, &Item)))
	{
		hr = _CreatePropertyStoreFactory(Item, Factory);
	}

	return hr;
}
STDMETHODIMP CSevenZipFolder::_CreatePropertyStoreFactory(UINT Index, CComPtr<CPropertyStoreFactory> & Factory)
{
	CSevenZipArchiveItem * Item = _Folder->GetItem(Index);

	if (Item != NULL)
	{
		return _CreatePropertyStoreFactory(Item, Factory);
	}

	return E_INVALIDARG;
}

STDMETHODIMP CSevenZipFolder::_CreatePropertyStoreFactory(CSevenZipArchiveItem * Item, CComPtr<CPropertyStoreFactory> & Factory)
{
	HRESULT hr;
	CComPtr<CPropertyStoreFactory> Result;

	if (SUCCEEDED(hr = CoCreate(Result)))
	{
		if (SUCCEEDED(hr = Result->Initialize(this, Item)))
		{
			Factory = Result;
		}
	}

	return hr;
}

STDMETHODIMP CSevenZipFolder::_GetDetailsEx(PCUITEMID_CHILD ItemID, REFPROPERTYKEY Property, CComPropVariant & Result)
{
	HRESULT hr = OutputDebugPropertyName(Property);

	if (CPropertyStoreFactory::IsCached(Property))
	{
		hr = GetPropertyFromIDList(ItemID, Property, &Result);
	}
	else
	{
		CSevenZipArchiveItem * Item;
		
		if (SUCCEEDED(hr = _GetItemByItemID(ItemID, &Item)))
		{
			CComPtr<CPropertyStoreFactory> Factory;

			if (SUCCEEDED(hr = _CreatePropertyStoreFactory(Item, Factory)))
			{
				CComPtr<IPropertyStore> Store;

				if (SUCCEEDED(hr = Factory->GetPropertyStoreForKeys(&Property, 1, GPS_DEFAULT, IID_PPV_ARGS(&Store))))
				{
					hr = Store->GetValue(Property, &Result);
				}
			}
		}
	}

	return hr;
}

STDMETHODIMP CSevenZipFolder::_GetUIObjectOfItem(CSevenZipArchiveItem * Item, REFIID InterfaceID, VOID * * Pointer)
{
	BOOL    IsFolder;
	HRESULT hr;

	if (SUCCEEDED(hr = Item->IsFolder(&IsFolder)))
	{
		hr = E_NOINTERFACE;

		if (InterfaceID == IID_IExtractIconW)
		{
			if (IsFolder)
			{
				hr = _CreateFolderIExtractIcon(InterfaceID, Pointer);
			}
			else
			{
				CStringW FileName;

				if (SUCCEEDED(hr = Item->GetName(FileName)))
				{
					hr = _CreateFileIExtractIcon(FileName, InterfaceID, Pointer);
				}
			}
		}
		else if (InterfaceID == IID_IQueryAssociations)
		{
			ASSOCIATIONELEMENT Associations[] =
			{
				{ IsFolder ? ASSOCCLASS_FOLDER : ASSOCCLASS_STAR, NULL, NULL },
			};

			hr = ::AssocCreateForClasses(Associations, ARRAYSIZE(Associations), InterfaceID, Pointer);
		}
	}

	return hr;
}

STDMETHODIMP CSevenZipFolder::_CreateItemID(UINT32 Index, PIDLIST_RELATIVE * ItemIDList)
{
	PITEMID_CHILD ItemID = NULL;
	HRESULT       Result = S_OK;

	if (SUCCEEDED(Result = _CreateItemID(Index, &ItemID)))
	{
		*ItemIDList = ItemID;
	}

	return Result;
}

STDMETHODIMP CSevenZipFolder::_CreateItemID(UINT32 Index, PITEMID_CHILD * ItemID)
{
	HRESULT hr = E_FAIL;
	CComPtr<CPropertyStoreFactory> PropertyStoreFactory;

	if (SUCCEEDED(hr = _CreatePropertyStoreFactory(Index, PropertyStoreFactory)))
	{
		CComPtr<IPropertyStore> PropertyStore;

		if (SUCCEEDED(hr = PropertyStoreFactory->GetPropertyStoreToCache(IID_PPV_ARGS(&PropertyStore))))
		{
			ZIPFOLDER_ITEM it = 
			{ 
				Index 
			};

			hr = CreateItemID(&it, PropertyStore, ItemID);	
		}
	}

	return hr;
}


STDMETHODIMP CSevenZipFolder::_GetItemByItemID(PCUIDLIST_RELATIVE ItemID, CSevenZipArchiveItem * * Item)
{
	return _GetItemByItemID(_Folder, ItemID, Item);
}

STDMETHODIMP CSevenZipFolder::_GetItemByItemID(CSevenZipArchiveItem * Parent, PCUIDLIST_RELATIVE ItemID, CSevenZipArchiveItem * * Item)
{
	HRESULT hr;
	PCZIPFOLDER_ITEM Data;

	if (SUCCEEDED(hr = GetDataFromIDList(ItemID, &Data)))
	{
		*Item = Parent->GetItem(Data->ItemIndex);
	}

	return hr;
}

STDMETHODIMP CSevenZipFolder::_GetItemByItemIDList(PCUIDLIST_RELATIVE ItemIDList, CSevenZipArchiveItem * * Item)
{
	return _GetItemByItemIDList(_Folder, ItemIDList, Item);
}

STDMETHODIMP CSevenZipFolder::_GetItemByItemIDList(CSevenZipArchiveItem * Parent, PCUIDLIST_RELATIVE ItemIDList, CSevenZipArchiveItem * * Item)
{
	if (Item != NULL)
	{
		HRESULT hr = S_OK;
		PCUIDLIST_RELATIVE Current = ItemIDList;

		while (SUCCEEDED(hr) && (Parent != NULL) && !::ILIsEmpty(Current))
		{
			if (SUCCEEDED(hr = _GetItemByItemID(Parent, Current, &Parent)))
			{
				Current = ::ILNext(Current);
			}
		}

		if (SUCCEEDED(hr))
		{
			*Item = Parent;
		}

		return hr;
	}

	return E_POINTER;
}


STDMETHODIMP CSevenZipFolder::_GetPathFromItemIDList(PCIDLIST_ABSOLUTE ItemIDList, CStringW & Path, CComMemPtr<ITEMIDLIST_RELATIVE> & Remaining)
{
	HRESULT hr = S_OK;

	WCHAR Buffer[MAX_PATH];
	PCUIDLIST_RELATIVE Current = ItemIDList;

	PIDLIST_RELATIVE SevenZipPart = NULL;
	PIDLIST_RELATIVE FileSystemPart = NULL;
	
	while (SUCCEEDED(hr) && !::ILIsEmpty(Current))
	{
		if (GetDataFromIDList(Current) != NULL)
		{
			SevenZipPart = ::ILClone(Current);
			break;
		}
		else
		{
			FileSystemPart = ::ILAppendID(FileSystemPart, &Current->mkid, TRUE);
			Current = ::ILNext(Current);
		}
	}

	if (::SHGetPathFromIDListW(reinterpret_cast<PIDLIST_ABSOLUTE>(FileSystemPart), Buffer))
	{
		Path = Buffer;
		Remaining.Attach(SevenZipPart);
	}
	else
	{
		hr = E_INVALIDARG;

		if (SevenZipPart != NULL)
		{
			::CoTaskMemFree(SevenZipPart);
		}
	}

	::CoTaskMemFree(FileSystemPart);

	return hr;
}



STDMETHODIMP CSevenZipFolder::_CreateFolderIExtractIcon(REFIID InterfaceID, VOID * * Pointer)
{
	HRESULT hr;
	CComPtr<IDefaultExtractIconInit> Extractor;

	if (SUCCEEDED(hr = ::SHCreateDefaultExtractIcon(IID_PPV_ARGS(&Extractor))))
	{
		if (SUCCEEDED(hr = Extractor->SetNormalIcon(NULL, SIID_FOLDER)))
		{
			if (SUCCEEDED(hr = Extractor->SetOpenIcon(NULL, SIID_FOLDEROPEN)))
			{
				hr = Extractor->QueryInterface(InterfaceID, Pointer);
			}
		}
	}

	return hr;
}

STDMETHODIMP CSevenZipFolder::_CreateFileIExtractIcon(CStringW & FileName, REFIID InterfaceID, VOID * * Pointer)
{
	HRESULT hr;
	WIN32_FIND_DATAW FindData = { };

	CComPtr<IBindCtx> BindCtx;

	if (SUCCEEDED(hr = CreateFileSysBindCtx(&FindData, &BindCtx)))
	{
		CComMemPtr<ITEMIDLIST_ABSOLUTE> ItemIDList;

		if (SUCCEEDED(hr = ::SHParseDisplayName(FileName, BindCtx, &ItemIDList, 0, NULL)))
		{
			CComPtr<IShellFolder> ParentFolder;
			PCUITEMID_CHILD       ChildID;

			if (SUCCEEDED(hr = ::SHBindToParent(ItemIDList, IID_PPV_ARGS(&ParentFolder), &ChildID)))
			{
				hr = ParentFolder->GetUIObjectOf(NULL, 1, &ChildID, InterfaceID, NULL, Pointer);
			}
		}
	}

	return hr;
}
OBJECT_ENTRY_AUTO(CLSID_SevenZipFolder, CSevenZipFolder)